/**
 * @file   proxy-conn.cpp
 * @author Alex Ott <alexott@gmail.com>
 * 
 * @brief  
 * 
 * 
 */

#include "proxy-conn.hpp"
//#include "httpsconnection.h"

/** 
 * 
 * 
 * @param io_service 
 */
connection::connection(ba::io_service& io_service, boost::asio::ssl::context& context) :
                                                     io_service_(io_service),
                                                     bsocket_(io_service),
                                                     ssocket_(io_service),
                                                     sslsocket_(io_service, context),
                                                     resolver_(io_service),
                                                     proxy_closed(false),
                                                     isPersistent(false),
                                                     isOpened(false),
                                                     https(false)
{
    fHeaders.reserve(8192);
}
/** 
 * Start read data of request from browser
 * 
 */
void connection::start() {
//  	std::cout << "start" << std::endl;
	fHeaders.clear();
	reqHeaders.clear();
	respHeaders.clear();
	
	handle_browser_read_headers(bs::error_code(), 0);
}

/** 
 * Read header of HTTP request from browser
 * 
 * @param err 
 * @param len 
 */
void connection::handle_browser_read_headers(const bs::error_code& err, size_t len) {
//  	std::cout << "handle_browser_read_headers. Error: " << err << ", len=" << len << std::endl;
    if(!err) {
		if(fHeaders.empty())
			fHeaders=std::string(bbuffer.data(),len);
        else
            fHeaders+=std::string(bbuffer.data(),len);
        if(fHeaders.find("\r\n\r\n") == std::string::npos) { // going to read rest of headers
			async_read(bsocket_, ba::buffer(bbuffer), ba::transfer_at_least(1),
					   boost::bind(&connection::handle_browser_read_headers,
								   shared_from_this(),
								   ba::placeholders::error,
								   ba::placeholders::bytes_transferred));
        } else { // analyze headers
//            std::cout << "fHeaders:\n" << fHeaders << std::endl;
			std::string::size_type idx=fHeaders.find("\r\n");
            std::string reqString=fHeaders.substr(0,idx);

            fHeadersFull = fHeaders;
			fHeaders.erase(0,idx+2);

			idx=reqString.find(" ");
			if(idx == std::string::npos) {
				std::cout << "Bad first line: " << reqString << std::endl;
				return;
			}
			
			fMethod=reqString.substr(0,idx);
			reqString=reqString.substr(idx+1);
			idx=reqString.find(" ");
			if(idx == std::string::npos) {
				std::cout << "Bad first line of request: " << reqString << std::endl;
				return;
			}
			fURL=reqString.substr(0,idx);
			fReqVersion=reqString.substr(idx+1);
			idx=fReqVersion.find("/");
			if(idx == std::string::npos) {
				std::cout << "Bad first line of request: " << reqString << std::endl;
				return;
			}
			fReqVersion=fReqVersion.substr(idx+1);
			
			// string outputs to console completely, even when using multithreading
            std::cout << std::string("\n fMethod: " + fMethod + ", fURL: " + fURL + ", fReqVersion: " + fReqVersion + "\n") << std::endl;
			// analyze headers, etc
			parseHeaders(fHeaders,reqHeaders);
			//
//            async_read(bsocket_, ba::buffer(bbuffer,bbuffer.size()), ba::transfer_at_least(1),
//                       boost::bind(&connection::handle_read_browser_body,
//                                   shared_from_this(),
//                                   ba::placeholders::error,
//                                   ba::placeholders::bytes_transferred));
            start_connect();
		}
	} else {
        if( !https )
            shutdown();
	}
}

/**
 * Handle reading body from browser
 *
 * @param err
 * @param len
 */
void connection::handle_read_browser_body(const bs::error_code& err, size_t len) {
    std::cout << "handle_read_browser_body" << std::endl;
    if(!err || err == ba::error::eof) {
        if(err == ba::error::eof) {
            std::cout << "start connect" << std::endl;
            start_connect();
        } else if( !err ) {
            fHeadersFull += std::string(bbuffer.data(),len);
            async_read(bsocket_, ba::buffer(bbuffer,bbuffer.size()), ba::transfer_at_least(1),
                   boost::bind(&connection::handle_read_browser_body,
                               shared_from_this(),
                               ba::placeholders::error,
                               ba::placeholders::bytes_transferred));
        }
    } else {
        if( !https )
            shutdown();
    }
}

/** 
 * Start connecting to the web-server, initially to resolve the DNS-name of web-server into the IP address
 * 
 */
void connection::start_connect() {
	std::string server="";
	std::string port="80";
	std::string fURLnoHTTP;

	std::string::size_type idx=fURL.find("http://");
	if(idx != std::string::npos)
		fURLnoHTTP=fURL.substr(idx+7);
	else {
		idx=fURL.find("https://");
		if(idx != std::string::npos)
			fURLnoHTTP=fURL.substr(idx+8);
		else
			fURLnoHTTP=fURL;
	}
	
	std::cout << "fURLnoHTTP: " << fURLnoHTTP << std::endl;

	idx=fURLnoHTTP.find(":");
	if(idx != std::string::npos ){
		server=fURLnoHTTP.substr(0,idx);
		port = "";

		int i = 1;
		char c=fURLnoHTTP.at(idx+i);

		while( isdigit(c) ) {
			port += c;
			i++;
			if( fURLnoHTTP.length() > (idx+i) )
				c=fURLnoHTTP.at(idx+i);
			else
				break;
		}
        idx=fURLnoHTTP.find("/");
        if( idx != std::string::npos ) {
            std::cout << "have /" << std::endl;
            fNewURL = fURLnoHTTP.substr(idx, fURLnoHTTP.length() - idx + 1);
        }
        else {
            fNewURL="/";
        }

	} else {
        idx=fURLnoHTTP.find("/");
        if( idx != std::string::npos ) {
            server=fURLnoHTTP.substr(0,idx);
            fNewURL = fURLnoHTTP.substr(idx, fURLnoHTTP.length() - idx + 1);
        }
        else {
            server=fURLnoHTTP;
            fNewURL=fURL;
        }
	}

	if(server.empty()) {
		std::cout << "Can't parse URL "<< std::endl;
		return;
	}
    std::cout << "\t\tserver: " << server << "port " << port << "newurl " << fNewURL << std::endl;
	
    if(!isOpened || server != fServer || port != fPort) {
		fServer=server;
		fPort=port;

        if( port == "443" ) {
            https = true;
            std::cout << "https connection" << std::endl;
            ba::ip::tcp::resolver::query query(server, "https");
            resolver_.async_resolve(query,
                                    boost::bind(&connection::handle_resolve_https, shared_from_this(),
                                                boost::asio::placeholders::error,
                                                boost::asio::placeholders::iterator));
        } else {
            ba::ip::tcp::resolver::query query(server, port);
            https = false;
            std::cout << "http connection" << std::endl;
            resolver_.async_resolve(query,
                                    boost::bind(&connection::handle_resolve, shared_from_this(),
                                                boost::asio::placeholders::error,
                                                boost::asio::placeholders::iterator));
        }
	} else {
        if( !https )
            start_write_to_server();
	}
}

/** 
 * If successful, after the resolved DNS-names of web-server into the IP addresses, try to connect
 * 
 * @param err 
 * @param endpoint_iterator 
 */
void connection::handle_resolve(const boost::system::error_code& err,
								ba::ip::tcp::resolver::iterator endpoint_iterator) {
//	std::cout << "handle_resolve. Error: " << err.message() << "\n";
    if (!err) {
		const bool first_time = true;
        handle_connect(boost::system::error_code(), endpoint_iterator, first_time);
    }else {
        if( !https )
            shutdown();
	}
}

/**
 * If successful, after the resolved DNS-names of web-server into the IP addresses for https, try to connect
 *
 * @param err
 * @param endpoint_iterator
 */
void connection::handle_resolve_https(const boost::system::error_code& err,
                                ba::ip::tcp::resolver::iterator endpoint_iterator) {
    if (!err) {
        std::cout << "handle_resolve_https" << std::endl;

        boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
        sslsocket_.lowest_layer().async_connect(endpoint,
                boost::bind(&connection::https_handle_connect, shared_from_this(),
                            boost::asio::placeholders::error, ++endpoint_iterator));
    }else {
        if( https )
            shutdownhttps();
    }
}
/** 
 * Try to connect to the web-server
 * 
 * @param err 
 * @param endpoint_iterator 
 */
void connection::handle_connect(const boost::system::error_code& err,
								ba::ip::tcp::resolver::iterator endpoint_iterator, const bool first_time) {
//	std::cout << "handle_connect. Error: " << err << "\n";
    if (!err && !first_time) {
		isOpened=true;
		start_write_to_server();
    } else if (endpoint_iterator != ba::ip::tcp::resolver::iterator()) {
		//ssocket_.close();
		ba::ip::tcp::endpoint endpoint = *endpoint_iterator;
		ssocket_.async_connect(endpoint,
                               boost::bind(&connection::handle_connect, shared_from_this(),
										   boost::asio::placeholders::error,
										   ++endpoint_iterator, false));
    } else {
        if( !https )
            shutdown();
	}
}

/** 
 * Write data to the web-server
 * 
 */
void connection::start_write_to_server() {
    fReq=fMethod;
	fReq+=" ";
	fReq+=fNewURL;
    fReq+=" HTTP/";
    fReq+="1.0";
//	fReq+=fReqVersion;
	fReq+="\r\n";
	fReq+=fHeaders;
//    std::cout << "Request: " << fReq << std::endl;
    ba::async_write(ssocket_, ba::buffer(fReq),
					boost::bind(&connection::handle_server_write, shared_from_this(),
								ba::placeholders::error,
								ba::placeholders::bytes_transferred));

	fHeaders.clear();
}

/** 
 * If successful, read the header that came from a web server
 * 
 * @param err 
 * @param len 
 */
void connection::handle_server_write(const bs::error_code& err, size_t len) {
// 	std::cout << "handle_server_write. Error: " << err << ", len=" << len << std::endl;
	if(!err) {
		handle_server_read_headers(bs::error_code(), 0);
	}else {
        if( !https )
            shutdown();
	}
}

/** 
 * Read header of data returned from the web-server
 * 
 * @param err 
 * @param len 
 */
void connection::handle_server_read_headers(const bs::error_code& err, size_t len) {
// 	std::cout << "handle_server_read_headers. Error: " << err << ", len=" << len << std::endl;
	if(!err) {
		std::string::size_type idx;
		if(fHeaders.empty())
			fHeaders=std::string(sbuffer.data(),len);
		else
			fHeaders+=std::string(sbuffer.data(),len);
		idx=fHeaders.find("\r\n\r\n");
		if(idx == std::string::npos) { // going to read rest of headers
			async_read(ssocket_, ba::buffer(sbuffer), ba::transfer_at_least(1),
					   boost::bind(&connection::handle_server_read_headers,
								   shared_from_this(),
								   ba::placeholders::error,
								   ba::placeholders::bytes_transferred));
		} else { // analyze headers
//            std::cout << "Response: " << fHeaders << std::endl;
			RespReaded=len-idx-4;
			idx=fHeaders.find("\r\n");
 			std::string respString=fHeaders.substr(0,idx);
			RespLen = -1;
			parseHeaders(fHeaders.substr(idx+2),respHeaders);
			std::string reqConnString="",respConnString="";

			std::string respVersion=respString.substr(respString.find("HTTP/")+5,3);
			
			headersMap::iterator it=respHeaders.find("Content-Length");
			if(it != respHeaders.end())
				RespLen=boost::lexical_cast<int>(it->second);
			it=respHeaders.find("Connection");
			if(it != respHeaders.end())
				respConnString=it->second;
			it=reqHeaders.find("Connection");
			if(it != reqHeaders.end())
				reqConnString=it->second;
			
			isPersistent=(
				((fReqVersion == "1.1" && reqConnString != "close") ||
				 (fReqVersion == "1.0" && reqConnString == "keep-alive")) &&
				((respVersion == "1.1" && respConnString != "close") ||
				 (respVersion == "1.0" && respConnString == "keep-alive")) &&
				RespLen != -1);
// 			std::cout << "RespLen: " << RespLen << " RespReaded: " << RespReaded
// 					  << " isPersist: " << isPersistent << std::endl;
			
			// sent data
			ba::async_write(bsocket_, ba::buffer(fHeaders),
							boost::bind(&connection::handle_browser_write,
										shared_from_this(),
										ba::placeholders::error,
										ba::placeholders::bytes_transferred));
		}
	} else {
        if( !https )
            shutdown();
	}
}

/** 
 * Writing data to the browser, are recieved from web-server
 * 
 * @param err 
 * @param len 
 */
void connection::handle_browser_write(const bs::error_code& err, size_t len) {
//   	std::cout << "handle_browser_write. Error: " << err << " " << err.message()
//  			  << ", len=" << len << std::endl;
	if(!err) {
		if(!proxy_closed && (RespLen == -1 || RespReaded < RespLen))
			async_read(ssocket_, ba::buffer(sbuffer,len), ba::transfer_at_least(1),
					   boost::bind(&connection::handle_server_read_body,
								   shared_from_this(),
								   ba::placeholders::error,
								   ba::placeholders::bytes_transferred));
		else {
//			shutdown();
            if(isPersistent && !proxy_closed) {
  				std::cout << "Starting read headers from browser, as connection is persistent" << std::endl;
  				start();
 			}
		}
	} else {
        if( !https )
            shutdown();
	}
}

/** 
 * Reading data from a Web server, for the writing them to the browser
 * 
 * @param err 
 * @param len 
 */
void connection::handle_server_read_body(const bs::error_code& err, size_t len) {
//   	std::cout << "handle_server_read_body. Error: " << err << " " << err.message()
//  			  << ", len=" << len << std::endl;
	if(!err || err == ba::error::eof) {
		RespReaded+=len;
// 		std::cout << "len=" << len << " resp_readed=" << RespReaded << " RespLen=" << RespLen<< std::endl;
		if(err == ba::error::eof)
            proxy_closed=true;
		ba::async_write(bsocket_, ba::buffer(sbuffer,len),
						boost::bind(&connection::handle_browser_write,
									shared_from_this(),
									ba::placeholders::error,
									ba::placeholders::bytes_transferred));
	} else {
        if( !https )
            shutdown();
	}
}

/** 
 * Close both sockets: for browser and web-server
 * 
 */
void connection::shutdown() {
    std::cout << "shutdown" << std::endl;
	ssocket_.close();
	bsocket_.close();
}


void connection::parseHeaders(const std::string& h, headersMap& hm) {
	std::string str(h);
	std::string::size_type idx;
	std::string t;
	while((idx=str.find("\r\n")) != std::string::npos) {
		t=str.substr(0,idx);
		str.erase(0,idx+2);
		if(t == "")
			break;
		idx=t.find(": ");
		if(idx == std::string::npos) {
			std::cout << "Bad header line: " << t << std::endl;
			break;
		}
// 		std::cout << "Name: " << t.substr(0,idx) 
// 				  << " Value: " << t.substr(idx+2) << std::endl;
		hm.insert(std::make_pair(t.substr(0,idx),t.substr(idx+2)));
	}
}



//===========================================================================================
//=====================================for https=============================================
//===========================================================================================
void connection::https_handle_connect(const boost::system::error_code& error,
    boost::asio::ip::tcp::resolver::iterator endpoint_iterator)
{
    std::cout << "rerere" << std::endl;
    if (!error) {
        std::cout << "handle_connect" << std::endl;

        sslsocket_.async_handshake(boost::asio::ssl::stream_base::client,
            boost::bind(&connection::https_handle_handshake, shared_from_this(),
              boost::asio::placeholders::error));

    } else if (endpoint_iterator != boost::asio::ip::tcp::resolver::iterator()) {
        std::cout << "in endpoint_iterator != boost::asio::ip::tcp::resolver::iterator" << std::endl;
        //sslsocket_.lowest_layer().close();

        boost::asio::ip::tcp::endpoint endpoint = *endpoint_iterator;
        sslsocket_.lowest_layer().async_connect(endpoint,
            boost::bind(&connection::https_handle_connect, shared_from_this(),
              boost::asio::placeholders::error, ++endpoint_iterator));
    } else {
        std::cout << "Connect failed: " << error << "\n";
        shutdownhttps();
    }
}

void connection::https_handle_handshake(const boost::system::error_code& error)
{
    if (!error)
    {
        std::cout << "handle_handshake" << std::endl;
        https_start_write_to_server();
    }
    else
    {
        std::cout << "Handshake failed: " << error << "\n";
    }
}

/**
 * Write data to the web-server
 *
 */
void connection::https_start_write_to_server() {
    if( fMethod == "CONNECT" )
        fReq="GET";
    else
        fReq=fMethod;
    fReq+=" ";
    fReq+=fNewURL;
    fReq+=" HTTP/";
    fReq+="1.0";
//	fReq+=fReqVersion;
    fReq+="\r\n";
//    fReq += "Accept: */*\r\n";
//    fReq += "Connection: close\r\n";
    fReq+=fHeaders;
//        fReq = "GET " + fNewURL + " HTTP/1.0\r\n";
//        fReq += "Host: " + fServer + "\r\n";

    std::cout << "Request: " << fReq << std::endl;
    fHeaders.clear();
    ba::async_write(sslsocket_, ba::buffer(fReq),
                    boost::bind(&connection::https_handle_server_write, shared_from_this(),
                                ba::placeholders::error,
                                ba::placeholders::bytes_transferred));
}

/**
 * If successful, read the header that came from a web server
 *
 * @param err
 * @param len
 */
void connection::https_handle_server_write(const bs::error_code& err, size_t len) {
// 	std::cout << "handle_server_write. Error: " << err << ", len=" << len << std::endl;
    if(!err) {
        https_handle_server_read_headers(bs::error_code(), 0);
    }else {
        if( https )
            shutdownhttps();
    }
}

/**
 * Read header of data returned from the web-server
 *
 * @param err
 * @param len
 */
void connection::https_handle_server_read_headers(const bs::error_code& err, size_t len) {
// 	std::cout << "handle_server_read_headers. Error: " << err << ", len=" << len << std::endl;
    if(!err) {
        std::string::size_type idx;
        if(fHeaders.empty())
            fHeaders=std::string(sbuffer.data(),len);
        else
            fHeaders+=std::string(sbuffer.data(),len);
        idx=fHeaders.find("\r\n\r\n");
        if(idx == std::string::npos) { // going to read rest of headers
            async_read(sslsocket_, ba::buffer(sbuffer), ba::transfer_at_least(1),
                       boost::bind(&connection::https_handle_server_read_headers,
                                   shared_from_this(),
                                   ba::placeholders::error,
                                   ba::placeholders::bytes_transferred));
        } else { // analyze headers
            std::cout << "Response: " << fHeaders << std::endl;
            RespReaded=len-idx-4;
            idx=fHeaders.find("\r\n");
            std::string respString=fHeaders.substr(0,idx);
            RespLen = -1;
            parseHeaders(fHeaders.substr(idx+2),respHeaders);
            std::string reqConnString="",respConnString="";

            std::string respVersion=respString.substr(respString.find("HTTP/")+5,3);

            headersMap::iterator it=respHeaders.find("Content-Length");
            if(it != respHeaders.end())
                RespLen=boost::lexical_cast<int>(it->second);
            it=respHeaders.find("Connection");
            if(it != respHeaders.end())
                respConnString=it->second;
            it=reqHeaders.find("Connection");
            if(it != reqHeaders.end())
                reqConnString=it->second;

            isPersistent=(
                ((fReqVersion == "1.1" && reqConnString != "close") ||
                 (fReqVersion == "1.0" && reqConnString == "keep-alive")) &&
                ((respVersion == "1.1" && respConnString != "close") ||
                 (respVersion == "1.0" && respConnString == "keep-alive")) &&
                RespLen != -1);
// 			std::cout << "RespLen: " << RespLen << " RespReaded: " << RespReaded
// 					  << " isPersist: " << isPersistent << std::endl;

            // sent data
            ba::async_write(bsocket_, ba::buffer(fHeaders),
                            boost::bind(&connection::https_handle_browser_write,
                                        shared_from_this(),
                                        ba::placeholders::error,
                                        ba::placeholders::bytes_transferred));
        }
    } else {
        if( https )
            shutdownhttps();
    }
}

/**
 * Writing data to the browser, are recieved from web-server
 *
 * @param err
 * @param len
 */
void connection::https_handle_browser_write(const bs::error_code& err, size_t len) {
    std::cout << "handle_browser_write. Error: " << err << " " << err.message()
              << ", len=" << len << std::endl;
    if(!err) {
        if(!proxy_closed && (RespLen == -1 || RespReaded < RespLen))
            async_read(sslsocket_, ba::buffer(sbuffer,len), ba::transfer_at_least(1),
                       boost::bind(&connection::https_handle_server_read_body,
                                   shared_from_this(),
                                   ba::placeholders::error,
                                   ba::placeholders::bytes_transferred));
        else {
//			shutdown();
            if(isPersistent && !proxy_closed) {
                std::cout << "Starting read headers from browser, as connection is persistent" << std::endl;
                start();
            }
        }
    } else {
        if( https )
            shutdownhttps();
    }
}

/**
 * Reading data from a Web server, for the writing them to the browser
 *
 * @param err
 * @param len
 */
void connection::https_handle_server_read_body(const bs::error_code& err, size_t len) {
    std::cout << "handle_server_read_body. Error: " << err << " " << err.message()
              << ", len=" << len << std::endl;
    if(!err || err == ba::error::eof) {
        RespReaded+=len;
// 		std::cout << "len=" << len << " resp_readed=" << RespReaded << " RespLen=" << RespLen<< std::endl;
        if(err == ba::error::eof)
            proxy_closed=true;
        ba::async_write(bsocket_, ba::buffer(sbuffer,len),
                        boost::bind(&connection::https_handle_browser_write,
                                    shared_from_this(),
                                    ba::placeholders::error,
                                    ba::placeholders::bytes_transferred));
    } else {
        if( https )
            shutdownhttps();
    }
}

/**
 * Close both sockets: for browser and web-server
 *
 */
void connection::shutdownhttps() {
    std::cout << "shutdown https" << std::endl;
    sslsocket_.lowest_layer().close();
    bsocket_.close();
}

//void connection::https_handle_write(const boost::system::error_code& error,
//    size_t bytes_transferred)
//{
//  if (!error)
//  {
//    boost::asio::async_read(sslsocket_,
//        boost::asio::buffer(reply_, bytes_transferred),
//        boost::bind(&connection::https_handle_read, shared_from_this(),
//          boost::asio::placeholders::error,
//          boost::asio::placeholders::bytes_transferred));
//  }
//  else
//  {
//    std::cout << "Write failed: " << error << "\n";
//  }
//}

//void connection::https_handle_read(const boost::system::error_code& error,
//    size_t bytes_transferred)
//{
//  if (!error)
//  {
//    std::cout << "Reply: ";
//    std::cout.write(reply_, bytes_transferred);
//    std::cout << "\n";
//  }
//  else
//  {
//    std::cout << "Read failed: " << error << "\n";
//  }
//}
